/*
 * Copyright Notice ====================================================
 * This file contains proprietary information of Hewlett-Packard Co.
 * Copying or reproduction without prior written approval is prohibited.
 * Copyright (c) 2013 All rights reserved. =============================
 */

package com.hp.ecs.sonar;

import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.persistence.Query;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.sonar.api.batch.PostJob;
import org.sonar.api.batch.SensorContext;
import org.sonar.api.config.Settings;
import org.sonar.api.database.DatabaseSession;
import org.sonar.api.database.model.RuleFailureModel;
import org.sonar.api.resources.Project;
import org.sonar.api.rules.Rule;
import org.sonar.api.rules.RulePriority;

import com.hp.ecs.sonar.email.MailSender;
import com.hp.ecs.sonar.util.DateUtil;
import com.hp.ecs.sonar.util.ScmUtil;
import com.hp.ecs.sonar.util.Violation;

public class Email2Owner implements PostJob {
	private static final Log LOG = LogFactory.getLog(Email2Owner.class);

	public static final String NO_OWNER = "NO_FOUND_OWNER";

	private DatabaseSession session;

	private Settings settings;

	private int days = 4;

	private String supervisor;

	public Email2Owner(DatabaseSession session, Settings settings) {
		this.settings = settings;
		this.session = session;
	}

	public void executeOn(Project project, SensorContext sensorContext) {
		LOG.info("Project.id: " + project.getId());
		try {
			this.readConfig();
			if (!MailSender.configured()) {
				LOG.warn("Should configure email sender firstly");
			}
			final Date date = DateUtil.addDays(DateUtil.currentDate(), -days);
			if (after(date, project)) {
				LOG.warn("No new Project. ");
				return;
			}
			Integer rootId = getRootId(project.getId());
			String hql = "select f from RuleFailureModel f where f.createdAt>:date2"
				+ " and(f.priority=:blocker or f.priority=:critical or f.priority=:major)"
				+ " and exists(select 1 from Snapshot s where"
				+ " s.id=f.snapshotId and s.rootId=:rootId) order by f.id desc";
			Query q = session.createQuery(hql);
			q.setParameter("date2", date);
			q.setParameter("blocker", RulePriority.BLOCKER);
			q.setParameter("critical", RulePriority.CRITICAL);
			q.setParameter("major", RulePriority.MAJOR);
			q.setParameter("rootId", rootId);
			List<RuleFailureModel> failures = q.getResultList();
			if (LOG.isInfoEnabled()) {
				LOG.info("failures.size:" + failures.size());
			}
			Map<String, List<Violation>> map = new HashMap();
			for (RuleFailureModel f : failures) {
				add2Map(f, map, date);
			}
			StringBuilder str = new StringBuilder();
			String buildTime = "Build Time:" + DateUtil.currentDate();
			int totalCount = 0;
			for (Map.Entry<String, List<Violation>> me : map.entrySet()) {
				List<Violation> violations = me.getValue();
				String content = MailSender.toMailContent(violations);
				String owner = me.getKey();
				totalCount += violations.size();
				if (MailSender.configured()) {
					if (!NO_OWNER.equals(owner)) {
						MailSender.send(owner, violations.size() + " new violations from your code in " + days
							+ " days", buildTime + '\n' + content);
					}
					str.append("\n----------------------------------------------------------------\n");
					str.append(owner);
					str.append('(').append(violations.size()).append(")\n").append(content);
				} else {
					LOG.error("Failed to send " + violations.size() + " violations to " + owner
						+ ". Check the configuration of email, please.");
					LOG.warn(content);
				}
			}
			if (str.length() > 0) {
				String title = project.getName() + " has " + totalCount + " new violations in " + days + " days.";
				MailSender.send(this.supervisor, title, title + '\n' + buildTime + str);
			} else if (MailSender.configured()) {
				int oldViolationsCount = countOldViolations(rootId);
				if (oldViolationsCount == 0) {
					MailSender.send(this.supervisor, "Good news! " + project.getName() + " has no any violations",
						buildTime);
				} else {
					MailSender.send(this.supervisor, project.getName() + " has " + oldViolationsCount
						+ " old violations of " + days + " days ago.", buildTime);
				}
			}
		} catch (Exception e) {
			LOG.error(e, e);
		}
	}

	private int countOldViolations(Integer rootId) {
		String hql = "select count(f) from RuleFailureModel f where"
			+ "(f.priority=:blocker or f.priority=:critical or f.priority=:major)"
			+ " and exists(select 1 from Snapshot s where"
			+ " s.id=f.snapshotId and s.rootId=:rootId) order by f.id desc";
		Query q = session.createQuery(hql);
		q.setParameter("blocker", RulePriority.BLOCKER);
		q.setParameter("critical", RulePriority.CRITICAL);
		q.setParameter("major", RulePriority.MAJOR);
		q.setParameter("rootId", rootId);
		Number result = (Number)first(q);
		return result.intValue();
	}

	private Integer getRootId(Integer id) {
		String hql = "select max(s.id) from Snapshot s where s.rootId is null and s.rootProjectId=:projectId";
		Query q = session.createQuery(hql).setParameter("projectId", id);
		return (Integer)first(q);
	}

	private boolean after(Date date, Project project) {
		String hql = "select 1 from Snapshot s where s.createdAt<:date and s.resourceId=:projectId";
		Query query = session.createQuery(hql).setParameter("date", date).setParameter("projectId", project.getId());
		return first(query) == null;
	}

	private Object first(Query query) {
		try {
			return query.getSingleResult();
		} catch (javax.persistence.NoResultException e) {
			return null;
		}
	}

	private void readConfig() {
		days = settings.getInt(VpcPlugin.NEW_VIOLATION_DAYS);
		MailSender.setConfig(settings.getString(VpcPlugin.SMTP_HOST), settings.getString(VpcPlugin.SMTP_PORT),
			settings.getString(VpcPlugin.EMAIL_SENDER), settings.getString(VpcPlugin.PASSWORD),
			settings.getString(VpcPlugin.FROM));
		// emailSettings.getFrom()
		// emailSettings.getPrefix()
		this.supervisor = settings.getString(VpcPlugin.SUPERVISOR);

	}

	private void add2Map(RuleFailureModel f, Map<String, List<Violation>> map, Date date) {
		Violation v = new Violation();
		v.setSnapshotId(f.getSnapshotId());
		if (f.getLine() == null) {
			v.setLine(getLastLine(v.getSnapshotId()));
		} else {
			v.setLine(f.getLine());
		}
		final String author;
		final String time = getScm(v, "last_commit_datetimes_by_line");
		if (time == null) {
			author = NO_OWNER;
		} else {
			v.setTime(ScmUtil.trimTime(time));
			if (isOld(v, date)) {
				return;
			}
			author = getScm(v, "authors_by_line");
			if (f.getLine() != null) {
				v.setContent(getContent(f));
			}
		}
		v.setMessage(getMessage(f));
		v.setLevel(f.getPriority());
		v.setFile(getFile(f));
		List<Violation> list = map.get(author);
		if (list == null) {
			list = new ArrayList();
			map.put(author, list);
		}
		list.add(v);

	}

	private String getMessage(RuleFailureModel f) {
		if (f.getMessage() != null) {
			return f.getMessage();
		}
		String hql = "select r from Rule r where r.id=:id";
		Query q = session.createQuery(hql);
		q.setParameter("id", f.getRuleId());
		Rule r = (Rule)first(q);
		return r == null ? "Unkown" : r.getName();
	}

	private Integer getLastLine(Integer snapshotId) {
		String content = getSourceScm(snapshotId, "last_commit_datetimes_by_line");
		if (content == null) {
			return null;
		}
		String newLine = ScmUtil.getNewLine(content);
		return ScmUtil.getLineNo(newLine);
	}

	private boolean isOld(Violation v, Date date) {
		if (v.getTime().length() >= 10) {
			String dayStr = v.getTime().substring(0, 10);
			if (dayStr.matches("\\d{4}\\-\\d{2}-\\d{2}")) {
				if (DateUtil.parse(dayStr, DateUtil.DATE_PATTERN).getTime() + DateUtil.MILLIS_PER_DAY < date.getTime()) {
					return true;
				}
			}
		}
		return false;
	}

	private String getFile(RuleFailureModel f) {
		String hql = "select m.key from ResourceModel m where m.id=("
			+ "select s.resourceId from Snapshot s where s.id=:snapshotId)";
		Query query = session.createQuery(hql);
		return (String)first(query.setParameter("snapshotId", f.getSnapshotId()));
	}

	private String getContent(RuleFailureModel f) {
		String hql = "select s.data from SnapshotSource s where s.snapshotId=:snapshotId";
		Query query = session.createQuery(hql).setParameter("snapshotId", f.getSnapshotId());
		String data = (String)first(query);
		return ScmUtil.subLines(data, f.getLine() - 1, 3);
	}

	private String getScm(Violation v, String name) {
		String ss = getSourceScm(v.getSnapshotId(), name);
		if (ss == null) {
			return null;
		}
		String prefix = v.getLine() + "=";
		String s = ss.substring(ss.indexOf(prefix) + prefix.length());
		return s.substring(0, s.indexOf(';'));

	}

	private String getSourceScm(Integer snapShotId, String name) {
		String hql = "select m.id from Metric m where m.key=:key";
		Integer metricId = (Integer)first(session.createQuery(hql).setParameter("key", name));
		String hql2 = "select d.data from MeasureData d"
			+ " where d.measure.metricId=:metricId and d.snapshotId=:snapshotId";
		byte[] data = (byte[])first(session.createQuery(hql2).setParameter("snapshotId", snapShotId)
			.setParameter("metricId", metricId));
		if (data == null) {
			return null;
		}
		return new String(data);
	}

}
